<?php

namespace app\admin\service\common;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
use think\exception\ValidateException;
use think\facade\Db;

class ExportService
{
    // 定义支持的导出文件类型及其对应的Writer类
    const EXPORT_TYPES = [
        'xlsx' => Xlsx::class,
        'xls' => Xls::class,
    ];

    /**
     * 导出Excel文件
     * @param string $table 数据库表名
     * @param array $post 导出参数，包含ids, fields, fileName, type
     * @return array 返回文件信息，包含文件名和文件类型
     * @throws \Exception
     */
    public function exportExcel(string $table, array $post): array
    {
        // 1. 参数验证
        if (empty($table) || !is_string($table)) {
            throw new \InvalidArgumentException('Table name is required and must be a string.');
        }
        if (empty($post['fields']) || !is_array($post['fields'])) {
            throw new \InvalidArgumentException('Fields configuration is required and must be an array.');
        }
        if (empty($post['fileName']) || !is_string($post['fileName'])) {
            throw new \InvalidArgumentException('File name is required and must be a string.');
        }
        if (empty($post['type']) || !isset(self::EXPORT_TYPES[strtolower($post['type'])])) {
            throw new \InvalidArgumentException('Invalid or unsupported export type. Supported types are: ' . implode(', ', array_keys(self::EXPORT_TYPES)) . '.');
        }

        $query = Db::name($table);
        if (!empty($post['ids'])) {
            // 确保ids是数组，并过滤掉非数字项，防止SQL注入风险
            $ids = array_filter($post['ids'], 'is_numeric');
            if (!empty($ids)) {
                $query->whereIn('id', $ids);
            }
        }

        // 动态构建表头
        $headers = [];
        foreach ($post['fields'] as $fieldConfig) {
            $headers[] = $fieldConfig['title'] ?? $fieldConfig['field']; // 如果没有title，使用field作为标题
        }

        // 创建sheet对象
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();

        // 写入表头
        $sheet->fromArray([$headers], null, 'A1');

        // 动态填充数据 (考虑大数据量分批处理)
        $chunkSize = 1000; // 每次从数据库取1000条数据
        $page = 1;
        $startRow = 2; // 数据从第二行开始写入

        try {
            // 使用游标或分批查询以减少内存占用
            $totalRowsWritten = 0;
            while (true) {
                $offset = ($page - 1) * $chunkSize;
                $tableList = $query->limit($offset, $chunkSize)->select();

                if ($tableList->isEmpty()) {
                    break; // 没有更多数据了
                }

                $rowDataCollection = [];
                foreach ($tableList as $row) {
                    $rowData = [];
                    foreach ($post['fields'] as $fieldConfig) {
                        $fieldKeys = explode('.', $fieldConfig['field']);
                        $value = $row;
                        foreach ($fieldKeys as $key) {
                            // 确保键存在，避免PHP通知或警告
                            $value = is_array($value) && isset($value[$key]) ? $value[$key] : null;
                        }
                        $rowData[] = $value;
                    }
                    $rowDataCollection[] = $rowData;
                }

                // 批量写入数据
                $sheet->fromArray($rowDataCollection, null, 'A' . $startRow);
                $startRow += count($rowDataCollection);
                $totalRowsWritten += count($rowDataCollection);
                $page++;

                // 清理内存，对于大数据量非常重要
                unset($tableList, $rowDataCollection);
            }
        } catch (\Exception $e) {
            // 记录错误或抛出自定义异常
            throw new \Exception('Error fetching or processing data: ' . $e->getMessage(), 0, $e);
        }

        // 动态调整列宽
        $columnsCount = count($post['fields']);
        if ($columnsCount > 0) {
            for ($i = 0; $i < $columnsCount; $i++) {
                $columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i + 1);
                $sheet->getColumnDimension($columnLetter)->setAutoSize(true);
            }
        }

        // 文件名和类型
        $fileExtension = strtolower($post['type']);
        $filename = $post['fileName'] . '.' . $fileExtension;
        $writerType = self::EXPORT_TYPES[$fileExtension];

        // 将文件保存到临时目录，然后返回路径
        $tempFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('export_') . '.' . $fileExtension;
        try {
            $writer = new $writerType($spreadsheet);
            $writer->save($tempFilePath);
        } catch (\Exception $e) {
            throw new \Exception('Error saving Excel file: ' . $e->getMessage(), 0, $e);
        }

        // 清理Spreadsheet对象内存
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);

        return ['filename' => $filename, 'temp_file_path' => $tempFilePath];
    }

    /**
     * 处理Excel文件下载的HTTP响应
     * @param string $tempFilePath 临时文件路径
     * @param string $fileName 客户端下载的文件名
     * @param string $fileType 文件类型 (xls/xlsx)
     * @return void
     */
    public function downloadExcel(string $tempFilePath, string $fileName, string $fileType): void
    {
        if (!file_exists($tempFilePath)) {
            throw new \RuntimeException('File not found: ' . $tempFilePath);
        }

        $contentType = '';
        switch (strtolower($fileType)) {
            case 'xlsx':
                $contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                break;
            case 'xls':
                $contentType = 'application/vnd.ms-excel';
                break;
            default:
                throw new \InvalidArgumentException('Unsupported file type for download: ' . $fileType);
        }

        header('Content-Type: ' . $contentType);
        header('Content-Disposition: attachment;filename="' . $fileName . '"');
        header('Cache-Control: max-age=0');
        // 建议在生产环境限制Access-Control-Allow-Origin
         header('Access-Control-Allow-Origin: *');

        readfile($tempFilePath);

        // 下载完成后删除临时文件
        unlink($tempFilePath);
    }
}


